package com.luxk.db.server;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AccessControlException;
import java.util.Vector;

import com.luxk.db.common.Module;

public class DefaultServer implements Server {
	
	private boolean initialized = false;
	private StartupMode bootMode = StartupMode.NONE;
	private StartupMode curMode = StartupMode.NONE;
	  
	private int port = 10007;
	private String shutdownCommand = "SHUTDOWN";
	
	private String name = "DefaultServer";
	
	private Vector<Module> moduleReopsi = new Vector<Module>();
	
	public int getPort() {
		return this.port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	
	public String getShutdownCommand() {
		return this.shutdownCommand;
	}
	public void setShutdownCommand(String shutdownCommand) {
		this.shutdownCommand = shutdownCommand;
	}

	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}

	public void addModule(Module module) {
		if(module.getName() == null || "".equals(module.getName())) {
			throw new NullPointerException(
					"Module " + module.getClass().getName() + " name is null");
		}
		moduleReopsi.add(module);
		module.setServer(this);
	}
  
	public Module getModule(String name) {
		for(Module e: this.moduleReopsi) {
			if(e.getName().equals(name)) return e;
		}
		return null;
	}
	
	public void init() throws Exception {
		if(initialized)
			throw new IllegalStateException(name + " already initialized");
		
		for(Module e: this.moduleReopsi) e.init();
		
		initialized = true;
	}
	
	public ShutdownMode await() throws Exception {
		ServerSocket serverSocket = null;
		
		try {
			serverSocket = new ServerSocket(port, 1,
					InetAddress.getByName("127.0.0.1"));
		} catch(IOException e) {
			throw e;
		}

		Socket socket = null;
		InputStream in = null;
		StringBuffer buff = null;
		String command = null;
		int inSize = 1024;
		int ch = -1;

		while(true) {
			try {
				socket = serverSocket.accept();
				socket.setSoTimeout(10*1000);
				in = socket.getInputStream();
			} catch(AccessControlException e) {
				System.err.println(
						"Fail to accept server socket. Security exception");
				e.printStackTrace();
				continue;
			} catch(IOException e) {
				try { serverSocket.close(); } catch(Exception ee) {}
				throw e;
			}

			buff = new StringBuffer();
			inSize = 1024;

			while(inSize > 0) {
				ch = -1;
				try {
					ch = in.read();
				} catch(IOException e) {
					System.err.println( "Fail to read socket stream" );
					e.printStackTrace();
					continue;
				}

				// check control char or EOF
				if( ch < 32 ) break;

				buff.append((char)ch);
				inSize--;
			}

			try {
				socket.close();
			} catch(IOException e) {}

			command = buff.toString();

			if(this.shutdownCommand.equals(command)) {
				break;
			} else {
				System.err.println("Invalid command " + command);
			}
		}

		try {
			serverSocket.close();
		} catch(IOException e) {}
		
		return ShutdownMode.NORMAL;
	}
	
	public void startup(StartupMode mode) throws Exception {

		this.bootMode = mode;
		
		// 전체 모듈에 대해 단계별로 server mode를 차례차례 올린다
		// 단, 특정 단계에서 실패하면 전체 모듈이 직전 단계로 돌아가야 한다
		
		if(mode.getIntVal() >= StartupMode.NOMOUNT.getIntVal()) {
			try {
				for(Module e: this.moduleReopsi) e.raise(StartupMode.NOMOUNT);
				this.curMode = StartupMode.NOMOUNT;
			} catch(Exception e) {
				// LOG(CRITICAL, fail to raise NOMOUNT)
				for(Module ee: this.moduleReopsi) ee.cancelRaise();
				throw e;
			}
		}
		
		if(mode.getIntVal() >= StartupMode.MOUNT.getIntVal()) {
			try {
				for(Module e: this.moduleReopsi) e.raise(StartupMode.MOUNT);
				this.curMode = StartupMode.MOUNT;
			} catch(Exception e) {
				// LOG(CRITICAL, fail to raise MOUNT)
				for(Module ee: this.moduleReopsi) ee.cancelRaise();
				throw e;
			}
		}
		
		if(mode.getIntVal() == StartupMode.STANDBY.getIntVal()) {
			try {
				for(Module e: this.moduleReopsi) e.raise(StartupMode.STANDBY);
				this.curMode = StartupMode.STANDBY;
			} catch(Exception e) {
				// LOG(CRITICAL, fail to raise STANDBY)
				for(Module ee: this.moduleReopsi) ee.cancelRaise();
				throw e;
			}
		}
		
		if(mode.getIntVal() == StartupMode.NORMAL.getIntVal()) {
			try {
				for(Module e: this.moduleReopsi) e.raise(StartupMode.NORMAL);
				this.curMode = StartupMode.NORMAL;
			} catch(Exception e) {
				// LOG(CRITICAL, fail to raise NORMAL)
				for(Module ee: this.moduleReopsi) ee.cancelRaise();
				throw e;
			}
		}
		
		System.out.println("Server " + this.name + " startup with " +
				mode.getStrVal() + " mode.");
	}
	
	public void shutdown(ShutdownMode mode) throws Exception {
		for(Module e: this.moduleReopsi) e.shutdown(mode);
		System.out.println("Server " + this.name + " shutdown successfully.");
	}
	
	public StartupMode getBootMode() { return this.bootMode; }
	public StartupMode getCurrentMode() { return this.curMode; }
	
	public void runTest() throws Exception {
		System.out.println("Begin unit test with " +
				this.curMode.getStrVal() + " mode.");
		
		for(Module e: this.moduleReopsi)
			e.runTest(this.curMode);
		
		System.out.println("End unit test.");
	}
}
